package org.dubh.common.collect;

import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import com.google.common.collect.ForwardingIterator;
import com.google.common.collect.ForwardingList;
import com.google.common.collect.ForwardingListIterator;

/**
 * A {@link List} implementation that delegates to another list,
 * recording stack traces whenever modifications are made.<p>
 *
 * If a {@link ConcurrentModificationException} occurs while
 * iterating the list, the stack trace of the most recent
 * modification will be set as the cause of the exception.<p>
 *
 * This implementation is generally useful for diagnostic
 * purposes, but should not be used in implementation because
 * of the overhead of recording modification stack traces.<p>
 *
 * @author brian.duff@dubh.org
 */
final class AuditedWriteList<E> extends ForwardingList<E> {

    private static final long serialVersionUID = -6840112456037818602L;
    private static final int MAX_RECORDED_WRITES = 10;

    private final Collection<Throwable> lastModifications = new DiscardingBoundedQueue<Throwable>( MAX_RECORDED_WRITES );

    protected AuditedWriteList(List<E> delegate) {
        super(delegate);
    }

    private void audit() {
        lastModifications.add( new Throwable("List modified") );
    }

    private void setLastModificationAsCause(Throwable t) {
        for ( Throwable modification : lastModifications ) {
            t.initCause( modification );
            t = modification;
        }
    }

    @Override
    public ListIterator<E> listIterator() {
        // super.iterator() copies the modification count, and therefore must 
        // happen under lock for synchronized lists. We therefore treat this
        // as a write.
        return new AuditingListIterator<E>(super.listIterator());
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        // super.iterator() copies the modification count, and therefore must 
        // happen under lock for synchronized lists. We therefore treat this
        // as a write.
        return new AuditingListIterator<E>(super.listIterator(index));
    }

    @Override
    public Iterator<E> iterator() {
        // super.iterator() copies the modification count, and therefore must 
        // happen under lock for synchronized lists. We therefore treat this
        // as a write.
        audit(); 
        return new AuditingIterator<E>(super.iterator());
    }

    @Override
    public void add(int index, E element) {
        audit();
        super.add(index, element);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> elements) {
        audit();
        return super.addAll(index, elements);
    }

    @Override
    public E remove(int index) {
        audit();
        return super.remove(index);
    }

    @Override
    public E set(int index, E element) {
        audit();
        return super.set(index, element);
    }

    @Override
    public void clear() {
        audit();
        super.clear();
    }

    @Override
    public boolean remove(Object object) {
        audit();
        return super.remove(object);
    }

    @Override
    public boolean removeAll(Collection<?> collection) {
        audit();
        return super.removeAll(collection);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        audit();
        return super.retainAll(c);
    }

    @Override
    public boolean add(E object) {
        audit();
        return super.add(object);
    }

    private final class AuditingIterator<T> extends ForwardingIterator<T> {
        private static final long serialVersionUID = 4795402276488353332L;

        protected AuditingIterator(Iterator<T> delegate) {
            super(delegate);
        }

        @Override
        public T next() {
            try {
                return super.next();
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

        @Override
        public void remove() {
            try {
                super.remove();
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

    }

    private final class AuditingListIterator<T> extends ForwardingListIterator<T> {

        private static final long serialVersionUID = -8872890935297261552L;

        protected AuditingListIterator(ListIterator<T> delegate) {
            super(delegate);
        }

        @Override
        public void add(T element) {
            try {
                super.add(element);
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

        @Override
        public T previous() {
            try {
                return super.previous();
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

        @Override
        public void set(T element) {
            try {
                super.set(element);
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

        @Override
        public T next() {
            try {
                return super.next();
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

        @Override
        public void remove() {
            try {
                super.remove();
            } catch (ConcurrentModificationException e) {
                setLastModificationAsCause(e);
                throw e;
            }
        }

    }

}
